SwiftUI的基礎(chǔ)與應(yīng)用

平安喜樂

什么是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ū)別:

  1. 聲明式的我們需要提前聲明好每個view的各種狀態(tài)办素,以及狀態(tài)轉(zhuǎn)變的條件角雷。后續(xù)界面和用戶在互動時,系統(tǒng)會幫我們自動進(jìn)行狀態(tài)切換性穿;

  2. 指令式的我們需要給每個view先設(shè)置好默認(rèn)狀態(tài)勺三,后續(xù)界面和用戶在互動時,需要通過指令不停的去轉(zhuǎn)變view的狀態(tài)需曾;

  3. 因此聲明式的UI是提前聲明好各種狀態(tài)吗坚,系統(tǒng)會自動幫我們進(jìn)行狀態(tài)切換。指令式的UI是通過我們設(shè)定的指令來轉(zhuǎn)換狀態(tài)胯舷;

  4. 比如界面調(diào)整刻蚯、用戶交互绊含、機(jī)型適配桑嘶,UIKit都需要手動調(diào)整view,對于SwiftUI我們只需要聲明好我們想要的樣式躬充,系統(tǒng)會幫我們?nèi)フ{(diào)整view逃顶;

  5. 總結(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)

image.png
1)頁面整體結(jié)構(gòu)
image.png
  • 頁面整體使用ScrollView + VStack的布局方式寥袭;
  • 在VStack中定義需要縱向展示的子頁面路捧;
2)標(biāo)題頁布局
image.png
  • iOS15.0之后,SwiftUI推出了一個全新的屬性overlay传黄,用以在某一控件上添加子控件杰扫;
3)推薦游戲頁布局
image.png
  • 推薦游戲頁整體采用TabView + VStack的布局方式;
  • TabView提供了一個可以左右分頁滑動的列表界面膘掰;
  • 使用AsyncImage加載網(wǎng)絡(luò)圖片章姓;
  • 使用AsyncImage的overlay屬性添加子控件;
  • 子控件采用HStack布局识埋;
4)其他游戲頁布局
image.png
  • 其他游戲頁整體采用VStack布局凡伊;
  • 列表部分使用LazyVStack的布局方式(也可以使用List);
  • 列表item使用HStack布局;
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末窒舟,一起剝皮案震驚了整個濱河市系忙,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌惠豺,老刑警劉巖银还,帶你破解...
    沈念sama閱讀 207,113評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異耕腾,居然都是意外死亡见剩,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,644評論 2 381
  • 文/潘曉璐 我一進(jìn)店門扫俺,熙熙樓的掌柜王于貴愁眉苦臉地迎上來苍苞,“玉大人,你說我怎么就攤上這事狼纬「牵” “怎么了?”我有些...
    開封第一講書人閱讀 153,340評論 0 344
  • 文/不壞的土叔 我叫張陵疗琉,是天一觀的道長冈欢。 經(jīng)常有香客問我,道長盈简,這世上最難降的妖魔是什么凑耻? 我笑而不...
    開封第一講書人閱讀 55,449評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮柠贤,結(jié)果婚禮上香浩,老公的妹妹穿的比我還像新娘。我一直安慰自己臼勉,他們只是感情好邻吭,可當(dāng)我...
    茶點(diǎn)故事閱讀 64,445評論 5 374
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著宴霸,像睡著了一般囱晴。 火紅的嫁衣襯著肌膚如雪膏蚓。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 49,166評論 1 284
  • 那天畸写,我揣著相機(jī)與錄音驮瞧,去河邊找鬼。 笑死,一個胖子當(dāng)著我的面吹牛蛙讥,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 38,442評論 3 401
  • 文/蒼蘭香墨 我猛地睜開眼兜材,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了间坐?” 一聲冷哼從身側(cè)響起潮尝,我...
    開封第一講書人閱讀 37,105評論 0 261
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎理茎,沒想到半個月后黑界,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 43,601評論 1 300
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡皂林,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,066評論 2 325
  • 正文 我和宋清朗相戀三年朗鸠,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片础倍。...
    茶點(diǎn)故事閱讀 38,161評論 1 334
  • 序言:一個原本活蹦亂跳的男人離奇死亡烛占,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出沟启,到底是詐尸還是另有隱情忆家,我是刑警寧澤,帶...
    沈念sama閱讀 33,792評論 4 323
  • 正文 年R本政府宣布德迹,位于F島的核電站芽卿,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏胳搞。R本人自食惡果不足惜卸例,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,351評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望肌毅。 院中可真熱鬧筷转,春花似錦、人聲如沸芽腾。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,352評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽摊滔。三九已至阴绢,卻和暖如春店乐,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背呻袭。 一陣腳步聲響...
    開封第一講書人閱讀 31,584評論 1 261
  • 我被黑心中介騙來泰國打工眨八, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人左电。 一個月前我還...
    沈念sama閱讀 45,618評論 2 355
  • 正文 我出身青樓廉侧,卻偏偏與公主長得像,于是被迫代替她去往敵國和親篓足。 傳聞我的和親對象是個殘疾皇子段誊,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 42,916評論 2 344

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