什么是SwiftUI撩银?
SwiftUI是2019年WWDC大會上窘茁,蘋果發(fā)布的基于Swift語言構(gòu)建的全新UI框架瞪醋,開發(fā)者可通過它快速為所有的Apple平臺創(chuàng)建美觀成黄、動態(tài)的應(yīng)用程序玻墅;
SwiftUI的運(yùn)行速度優(yōu)于UIKit介牙,他減少了界面的層次結(jié)構(gòu),因此可以減少繪制步驟澳厢,并且他完全繞過了CoreAnimation环础,直接進(jìn)入Metal,可以有優(yōu)秀的渲染性能剩拢;
SwiftUI 就是?種聲明式的構(gòu)建界面的用戶接口工具包线得;
SwiftUI使用聲明式的語法構(gòu)建UI,我們只需要向系統(tǒng)聲明UI的View樣式徐伐,以及View如何轉(zhuǎn)換狀態(tài)贯钩,其他的過程都交給系統(tǒng)去處理;
聲明式語法和指令式語法的區(qū)別:
聲明式的我們需要提前聲明好每個view的各種狀態(tài)办素,以及狀態(tài)轉(zhuǎn)變的條件角雷。后續(xù)界面和用戶在互動時,系統(tǒng)會幫我們自動進(jìn)行狀態(tài)切換性穿;
指令式的我們需要給每個view先設(shè)置好默認(rèn)狀態(tài)勺三,后續(xù)界面和用戶在互動時,需要通過指令不停的去轉(zhuǎn)變view的狀態(tài)需曾;
因此聲明式的UI是提前聲明好各種狀態(tài)吗坚,系統(tǒng)會自動幫我們進(jìn)行狀態(tài)切換。指令式的UI是通過我們設(shè)定的指令來轉(zhuǎn)換狀態(tài)胯舷;
比如界面調(diào)整刻蚯、用戶交互绊含、機(jī)型適配桑嘶,UIKit都需要手動調(diào)整view,對于SwiftUI我們只需要聲明好我們想要的樣式躬充,系統(tǒng)會幫我們?nèi)フ{(diào)整view逃顶;
總結(jié)起來,SwiftUI比UIKit更加抽象化充甚;
1)SwiftUI的優(yōu)點(diǎn)
1.1)統(tǒng)一蘋果終端
在 SwiftUI 出現(xiàn)之前以政,蘋果不同的設(shè)備之前的開發(fā)框架并不互通,增加開發(fā)者所需消耗的時間精力伴找,也不利于構(gòu)建跨平臺的軟件體驗(yàn)盈蛮;
SwiftUI具有了跨平臺性,蘋果的平臺都可以使用技矮,iOS抖誉、macOS殊轴、tvOS、watchOS袒炉;
1.2)降低界面開發(fā)難度
UIKit 的基本思想要求ViewController 承擔(dān)絕?部分職責(zé)旁理,它需要協(xié)調(diào) model,view 以及??交互我磁。這帶來了巨?的sideeffect 以及?量的狀態(tài)孽文;
SwiftUI是聲明式的構(gòu)建方式,我們只需要聲明好界面系統(tǒng)會自動轉(zhuǎn)換狀態(tài)夺艰,搭建界面更加的簡單芋哭;
1.3)更加高效
默認(rèn)使用Metal渲染,性能非常高,比UIKit要好郁副;
更扁平化的內(nèi)聯(lián)數(shù)據(jù)結(jié)構(gòu)去分配內(nèi)存楷掉,值類型。占用內(nèi)存很少(所以在輕應(yīng)用的開發(fā)更適合使用SwiftUI)霞势;
代碼量相比UIKit要更少烹植,效率更高;
1.4)更好的配合Swift語言
SwiftUI 使用了大量 Swift 的語言特性愕贡;
2)SwiftUI的特性
2.1)聲明式語法
與UIKit布局相比草雕,更加的抽象化,只需要向系統(tǒng)聲明界面樣式以及樣式變化條件固以,其他的系統(tǒng)會幫我們實(shí)現(xiàn)墩虹,不需要我們自己去調(diào)整視圖;
2.2)界面元素的組件化
UIKit耦合了很多的操作邏輯憨琳,很難進(jìn)行移植诫钓,更遑論組件化了;
而SwiftUI僅僅聲明界面樣式篙螟,所以是可以將復(fù)雜視圖的拆分出來組件化菌湃;
甚至還可以在其他平臺使用,以此跨平臺遍略;
一般我個人會將視圖組件區(qū)分為基礎(chǔ)組件惧所、布局組件和功能組件;
2.3)與UIKit互相兼容
把 UIKit 中已有的部分進(jìn)行封裝绪杏,提供給 SwiftUI 使用下愈。開發(fā)者需要做的僅僅是遵循UIViewRepresentable協(xié)議即可;
并且在已有的項(xiàng)目中蕾久,也可以僅用 SwiftUI 制作一部分的 UI 界面势似;
兩種代碼的風(fēng)格是截然不同的,但在使用上卻基本沒有性能的損失。在最終的運(yùn)行效果上履因,用戶也無法分辨出兩種界面框架的不同辖佣;
2.4)真實(shí)數(shù)據(jù)源(Source of truth)(重點(diǎn)特性)
SwiftUI中的數(shù)據(jù)源一定會是真實(shí)的,也就是準(zhǔn)確的搓逾;
在UIKit中卷谈,一個view的狀態(tài)由多種因素導(dǎo)致的,不同的來源霞篡,不同的邏輯操作(因此需要考慮及時更新界面)世蔗;
在SwiftUI中,只要在屬性聲明時加上@State關(guān)鍵詞朗兵,就可以將該屬性和界面元素聯(lián)系起來污淋,在每次數(shù)據(jù)改動后,都有機(jī)會決定是否更新視圖余掖,系統(tǒng)將所有的屬性都集中到一起進(jìn)行管理和計(jì)算寸爆,也不再需要手寫刷新的邏輯;
2.5)設(shè)計(jì)工具和快速預(yù)覽功能
Xcode 包含直觀的設(shè)計(jì)工具,只需拖放操作就能使用 SwiftUI 輕松構(gòu)建界面盐欺,同時支持實(shí)時預(yù)覽頁面的變化赁豆;
SwiftUI中常用的View和Modifiers
SwiftUI通過View視圖搭建界面,使用Modifiers修飾器來修飾視圖冗美。系統(tǒng)提供了大量的視圖和修飾器魔种,并且還可以讓我們自定義修飾器。
既可以手動寫粉洼,也可以直接拖出到代碼區(qū)或者預(yù)覽區(qū)节预。這三種方式的結(jié)果都是一樣的。
1)Text
顯示一行或多行的只讀文本視圖属韧,類似于UIKit中的UIlabel安拟;
Text("我是一個Text").foregroundColor(.red)
2)Label
顯示一個標(biāo)簽組件,支持圖片與標(biāo)題的展示宵喂;
Label("Rain", systemImage: "cloud.rain")
3)Button
顯示一個按鈕組件糠赦,類似于UIKit中的UIButton;
Button {
print("button點(diǎn)擊響應(yīng)")
} label: {
Text("我是按鈕")
}
4)Link
通過提供目標(biāo)URL和標(biāo)題來創(chuàng)建鏈接樊破;
Link(destination: URL(string:"https://www.baidu.com/")!) {
Text("Link")
}
5)Image
顯示一個圖片組件愉棱;
Image("image name")
.resizable()
.aspectRatio(contentMode: .fit)
也可以通過AsyncImage實(shí)現(xiàn)異步加載網(wǎng)絡(luò)圖片的組件唆铐;
AsyncImage(url: URL(string: "image url")) { image in
image.resizable()
} placeholder: {
Image("placeHolder image")
}
6)TextEditor
顯示可編輯文本界面的控件哲戚。相當(dāng)于UITextView;
TextEditor(text:
.constant("Placeholder"))
.frame(width: 100, height: 30, alignment: .center)
7)TextField
顯示文本輸入框艾岂。相當(dāng)于UITextView顺少;
TextField("首字母默認(rèn)大寫", text: $str).frame(width: 100, height: 56, alignment: .center)
.textInputAutocapitalization(.never)
.onSubmit {
print("我點(diǎn)擊了!")
}
textInputAutocapitalization為設(shè)置自動大小寫的屬性;
8)NavigationView
用于做頁面間的導(dǎo)航跳轉(zhuǎn)脆炎;
var body: some View {
NavigationView{
List{
VStack {
...
}
}.navigationBarTitle("Todo Items")
}
}
- 使用navigationBarTitle方法給控件設(shè)置導(dǎo)航欄的標(biāo)題梅猿;
- 注意navigationBarTitle修飾符屬于列表視圖,而不是導(dǎo)航視圖秒裕;
- 這是因?yàn)閷?dǎo)航視圖從右邊通過push來顯示新界面袱蚓;
- 每個界面都有自己的標(biāo)題。如果標(biāo)題是附加到導(dǎo)航視圖几蜻,標(biāo)題就被固定了喇潘;
- 通過附加的標(biāo)題到導(dǎo)航視圖的里面內(nèi)容,標(biāo)題可以更改為其內(nèi)容的變化梭稚;
NavigationView {
VStack {
...
NavigationLink(destination:
VStack {
Text(todo.name)
Image(todo.category).resizable().frame(width: 200, height: 200)
}
){
HStack {
...
}
}
}.navigationBarTitle("Nav Title")
}
- 注意颖低,我們必須向NavigationLink提供一個參數(shù)destination,也就是點(diǎn)擊項(xiàng)目時顯示的視圖;
- 這里代碼中可以看到,視圖將包括:Text和Image;
- 當(dāng)運(yùn)行應(yīng)用程序弧烤,點(diǎn)擊一個item就會跳轉(zhuǎn)到另一個界面忱屑,界面顯示選擇的項(xiàng)目的詳細(xì)信息;
- 新界面的頂部欄也會顯示帶有上一個項(xiàng)目的符號;
SwiftUI中的布局
UI通常由多種不同類型的視圖組合而成。我們?nèi)绾螌λ麄冞M(jìn)行分組以及布局定位暇昂?此時就需要使用stacks莺戒。我們可以使用三種堆棧來對UI進(jìn)行分組:
- HStack - 水平排列其子視圖;
- VStack - 垂直排列其子視圖急波;
- ZStack -根據(jù)深度排列子視圖(例如從后到前)脏毯;
在這三種Stack的基礎(chǔ)上還有一種懶加載的Stack,叫l(wèi)azyStack幔崖;
除此之外還需要了解絕地位置和相對位置的使用食店;
注意: SwiftUI沒有坐標(biāo)系這種說法,使用彈性布局赏寇。類似于HTML的布局方式吉嫩;
SwiftUI中List的使用
1)List的創(chuàng)建
var body: some View {
List{
HStack{
Image("work").resizable().frame(width: 50, height: 50)
Text("Write SwiftUI book")
}
HStack{
Image("personal").resizable().frame(width: 50, height: 50)
Text("Read Bible")
}
HStack{
Image("family").resizable().frame(width: 50, height: 50)
Text("Bring kids out to play")
}
HStack{
Image("family").resizable().frame(width: 50, height: 50)
Text("Fetch wife")
}
HStack{
Image("family").resizable().frame(width: 50, height: 50)
Text("Call mum")
}
}
}
- 通過List添加多行數(shù)據(jù);
- 每一行包含一個圖像和一個水平文本嗅定,通過HStack來包裝自娩;
- 因?yàn)閳D像大小不同,大的圖像會被擴(kuò)展渠退,除了屏幕大小忙迁,只顯示了一部分。為了解決這個問題,我們應(yīng)用. resizable修改器使圖像適合于使用面積碎乃;
- 然后應(yīng)用.frame修飾符將圖像的大小限制為一個自定義的框架姊扔;
2)List的動態(tài)性
可通過@State修飾數(shù)據(jù)源實(shí)現(xiàn)List列表的實(shí)時刷新;
3)ID標(biāo)識
通過ForEach構(gòu)建List元素時可以為每一個item設(shè)置id梅誓,一般可以通過數(shù)據(jù)源內(nèi)對應(yīng)該item的數(shù)據(jù)中的內(nèi)容定義id恰梢,也可以直接使用數(shù)據(jù)本身self佛南;
var body: some View {
List{
ForEach(datas, id:\.name){ data in
HStack{
Image(datas.category) .resizable().frame(width: 50, height: 50)
Text(datas.name)
}
}
}
}
SwiftUI中的屬性包裝器
1)@State
SwiftUI管理聲明為state的存儲屬性。當(dāng)值發(fā)生變化時嵌言,SwiftUI會更新視圖層次結(jié)構(gòu)中依賴于該值的部分嗅回。使用@State作為存儲在視圖層次結(jié)構(gòu)中的給定值的唯一真值來源;
@State修飾的屬性雖然是存儲屬性摧茴,但是我們可以進(jìn)行讀寫操作绵载;
父視圖和子視圖進(jìn)行傳遞該屬性只能是值傳遞;
需要在屬性名稱前加上一個美元符號$來獲得這個值苛白;
struct ContentView: View {
@State private var str: String = ""
var body: some View {
VStack {
TextField("Placeholder", text: $str)
Text("\(str)")
}
}
}
- 在str上設(shè)置了@State修飾尘分,那么我在文本輸入框中輸入的數(shù)據(jù),就會傳入到str中丸氛;
- 同時str又綁定在文本視圖上培愁,所以會將文本輸入框輸入的文本顯示到文本視圖上;
- 這就是數(shù)據(jù)綁定的快捷實(shí)現(xiàn)缓窜;
2)@Binding
@State修飾的屬性是值傳遞定续,因此在父視圖和子視圖之間傳遞屬性時。子視圖針對屬性的修改無法傳遞到父視圖上禾锤;
Binding修飾后會將屬性會變?yōu)橐粋€引用類型私股,視圖之間的傳遞從值傳遞變?yōu)榱艘脗鬟f,將父視圖和子視圖的屬性關(guān)聯(lián)起來恩掷。這樣子視圖針對屬性的修改倡鲸,會傳遞到父視圖上;
需要在屬性名稱前加上一個美元符號$來獲得這個值黄娘。因?yàn)樗峭队皩傩裕?/p>
下面代碼在主視圖上添加一個BtnView視圖峭状,視圖上添加一個按鈕,按鈕點(diǎn)擊后修改isShowText變量逼争。這里的變量通過傳入?yún)?shù)與主視圖的isShowText進(jìn)行綁定优床。綁定到主視圖的isShowText變量上。主視圖的變量用來決定文本視圖的隱藏和顯示誓焦;
struct BtnView: View {
@Binding var isShowText: Bool
var body: some View {
Button {
isShowText.toggle()
} label: {
Text("點(diǎn)擊")
}
}
}
struct ContentView: View {
@State private var isShowText: Bool = true
var body: some View {
VStack {
if(isShowText) {
Text("點(diǎn)擊后會被隱藏")
} else {
Text("點(diǎn)擊后會被顯示").hidden()
}
BtnView(isShowText: $isShowText)
}
}
}
- 按鈕在BtnView視圖中胆敞,并且通過點(diǎn)擊,修改isShowText的值杂伟;
- 將BtnView視圖添加到ContentView上作為它的子視圖移层。并且傳入isShowText;
- 此時的傳值是指針傳遞赫粥,會將點(diǎn)擊后的屬性值傳遞到父視圖上观话;
- 父視圖拿到后也作用在自己的屬性,因此他的文本視圖會依據(jù)該屬性而隱藏或顯示傅是;
- 如果將@Binding改為@State匪燕,會發(fā)現(xiàn)點(diǎn)擊后不起作用蕾羊。這是因?yàn)橹祩鬟f子視圖的更改不會反映到父視圖上喧笔;
3)@ObservableObject
對實(shí)例進(jìn)行監(jiān)聽帽驯,其用處和@State非常相似,只不過必須是對象书闸,而且這個被監(jiān)聽的對象可以被多個視圖使用尼变。需要注意用法;
class DelayedUpdater: ObservableObject {
@Published var value = 0
init() {
for i in 1...10 {
DispatchQueue.main.asyncAfter(deadline: .now() + Double(i)) {
self.value += 1
}
}
}
}
struct ContentView: View {
@ObservedObject var updater = DelayedUpdater()
var body: some View {
VStack {
Text("\(updater.value)").padding()
}
}
}
- 綁定的數(shù)據(jù)是一個對象浆劲;
- 被修飾的對象嫌术,其類必須遵守ObservableObject協(xié)議;
- 此時這個類中被@Published修飾的屬性都會被綁定牌借;
- 使用@ObservedObject修飾這個對象度气,綁定這個對象;
- 被@Published修飾的屬性發(fā)生改變時膨报,SwiftUI就會進(jìn)行更新磷籍;
- 這里當(dāng)value值會隨著時間發(fā)生改變。所以updater對象也會發(fā)生改现柠,此時文本視圖的內(nèi)容就會不斷更新院领;
4)@EnvironmentObject
在多視圖中,為了避免數(shù)據(jù)的無效傳遞够吩,可以直接將數(shù)據(jù)放到環(huán)境中比然,供多個視圖進(jìn)行使用,相當(dāng)于全局?jǐn)?shù)據(jù)周循;
struct EnvView: View {
@EnvironmentObject var updater: DelayedUpdater
var body: some View {
Text("\(updater.value)")
}
}
struct BtnvView: View {
@EnvironmentObject var updater: DelayedUpdater
var body: some View {
Text("\(updater.value)")
}
}
struct ContentView: View {
let updater = DelayedUpdater()
var body: some View {
VStack {
EnvView().environmentObject(updater)
BtnvView().environmentObject(updater)
}
}
}
- 給屬性添加@EnvironmentObject修改强法,就將其放到了環(huán)境中;
- 其他視圖中想要獲取該屬性湾笛,可以通過.environmentObject從環(huán)境中獲饶馓獭;
- 可以看到分別將EnvView和BtnvView的屬性分別放到了環(huán)境中迄本;
- 之后我們ContentView視圖中獲取數(shù)據(jù)時硕淑,可以直接通過環(huán)境獲取嘉赎;
- 不需要將數(shù)據(jù)傳遞到ContentView置媳,而是直接通過環(huán)境獲取,這樣避免了無效的數(shù)據(jù)傳遞公条,更加高效拇囊;
- 如果是在多層級視圖之間進(jìn)行傳遞,會有更明顯的效果靶橱;
SwiftUI-Demo-仿App Store頁面的實(shí)現(xiàn)
1)頁面整體結(jié)構(gòu)
- 頁面整體使用ScrollView + VStack的布局方式寥袭;
- 在VStack中定義需要縱向展示的子頁面路捧;
2)標(biāo)題頁布局
- iOS15.0之后,SwiftUI推出了一個全新的屬性overlay传黄,用以在某一控件上添加子控件杰扫;
3)推薦游戲頁布局
- 推薦游戲頁整體采用TabView + VStack的布局方式;
- TabView提供了一個可以左右分頁滑動的列表界面膘掰;
- 使用AsyncImage加載網(wǎng)絡(luò)圖片章姓;
- 使用AsyncImage的overlay屬性添加子控件;
- 子控件采用HStack布局识埋;
4)其他游戲頁布局
- 其他游戲頁整體采用VStack布局凡伊;
- 列表部分使用LazyVStack的布局方式(也可以使用List);
- 列表item使用HStack布局;