本篇文章講解的id()
,大家可能并沒有使用過胀滚,但了解這個(gè)技術(shù)权旷,在特定的場(chǎng)景下椭蹄,會(huì)幫助我們解決一些重要的問題杨何。
可在此處下載本篇文章所用代碼https://gist.github.com/agelessman/1cef9b682995329b5fa7b21df389c8ac
什么是id()
struct Example1: View {
@State private var text = ""
@State private var textFieldId = 0
var body: some View {
VStack {
TextField("請(qǐng)輸入郵箱", text: $text)
.id(textFieldId)
}
.padding(.horizontal, 20)
.textFieldStyle(RoundedBorderTextFieldStyle())
}
}
我們看一下id()的定義:
extension View {
/// Returns a view whose identity is explicitly bound to the proxy
/// value `id`. When `id` changes the identity of the view (for
/// example, its state) is reset.
@inlinable public func id<ID>(_ id: ID) -> some View where ID : Hashable
}
可以看出來,當(dāng)我們使用id()為某個(gè)view綁定了一個(gè)唯一的標(biāo)識(shí)后哩陕,當(dāng)該標(biāo)識(shí)的值改變后平项,表面上看赫舒,該view就會(huì)回到初始狀態(tài),實(shí)際上闽瓢,當(dāng)標(biāo)識(shí)改變后接癌,系統(tǒng)創(chuàng)建了一個(gè)新的view。
重置狀態(tài)
按照正常邏輯扣讼,當(dāng)我們標(biāo)識(shí)了某個(gè)view后缺猛,我們可以隨意控制該view,比如把view移動(dòng)到不同的容器中椭符,像變量一樣的使用view荔燎。但實(shí)際并不是這么回事。我們先看一個(gè)例子:
這個(gè)一個(gè)非常簡單的例子销钝,當(dāng)點(diǎn)擊重置按鈕后有咨,TextField重置狀態(tài),按照正常邏輯蒸健,我們的代碼應(yīng)該是這樣的:
struct Example1: View {
@State private var text = ""
@State private var textFieldId = 0
var body: some View {
VStack {
TextField("請(qǐng)輸入郵箱", text: $text)
.id(textFieldId)
Button("重置") {
self.text = ""
}
}
.padding(.horizontal, 20)
.textFieldStyle(RoundedBorderTextFieldStyle())
}
}
我們使用self.text = ""
清空了輸入框中的內(nèi)容座享,在這個(gè)例子中,我們Example1中的狀態(tài)并不多似忧,只有兩個(gè):
@State private var text = ""
@State private var textFieldId = 0
但是征讲,如果,狀態(tài)很多呢橡娄?類似下邊這種情況:
struct Example1: View {
@State private var text0 = ""
@State private var text1 = ""
@State private var text2 = ""
@State private var text3 = ""
@State private var text4 = ""
@State private var text5 = ""
@State private var text6 = ""
@State private var textFieldId = 0
var body: some View {
VStack {
VStack {
TextField("text0", text: $text0)
TextField("text1", text: $text1)
TextField("text2", text: $text2)
TextField("text3", text: $text3)
TextField("text4", text: $text4)
TextField("text5", text: $text5)
TextField("text6", text: $text6)
}
Button("重置") {
self.text0 = ""
self.text1 = ""
self.text2 = ""
self.text3 = ""
self.text4 = ""
self.text5 = ""
}
}
.padding(.horizontal, 20)
.textFieldStyle(RoundedBorderTextFieldStyle())
}
}
我們?cè)谇蹇盏臅r(shí)候,就會(huì)寫很多重復(fù)的代碼诗箍,如果我們給TextField外層的VStack綁定一個(gè)標(biāo)識(shí),重置這個(gè)操作就非常簡單挽唉。
struct Example1: View {
@State private var text0 = ""
...
@State private var textFieldId = 0
var body: some View {
VStack {
VStack {
TextField("text0", text: $text0)
...
TextField("text6", text: $text6)
}
.id(textFieldId)
Button("重置") {
self.textFieldId += 1
}
}
...
}
}
上邊的代碼中的...
是我省略掉了部分代碼滤祖,我們只關(guān)注核心部分。代碼看上去特別簡單瓶籽,但當(dāng)我們運(yùn)行后匠童,發(fā)現(xiàn):
點(diǎn)擊并沒有任何清空效果,我們修改下代碼塑顺,把這些TextField放到一個(gè)獨(dú)立的View中:
struct MyCustom: View {
@State private var text0 = ""
@State private var text1 = ""
@State private var text2 = ""
@State private var text3 = ""
@State private var text4 = ""
@State private var text5 = ""
@State private var text6 = ""
var body: some View {
VStack {
TextField("text0", text: $text0)
TextField("text1", text: $text1)
TextField("text2", text: $text2)
TextField("text3", text: $text3)
TextField("text4", text: $text4)
TextField("text5", text: $text5)
TextField("text6", text: $text6)
}
.padding(.horizontal, 20)
.textFieldStyle(RoundedBorderTextFieldStyle())
}
}
struct Example2: View {
@State private var textFieldId = 0
var body: some View {
VStack {
MyCustom()
.id(textFieldId)
Button("重置") {
self.textFieldId += 1
}
}
}
}
通過上邊的實(shí)驗(yàn)汤求,我們總結(jié)出以下幾點(diǎn):
- 宏觀上,修改id()严拒,可以把該view重置到初始狀態(tài)
- 所謂的重置到初始狀態(tài)扬绪,本質(zhì)上是重新創(chuàng)建了一個(gè)新的view
- 需要重置的view必須是一個(gè)獨(dú)立封裝的view
第3點(diǎn)很重要,如果我們想通過這種方式來重置view裤唠,我們就需要把該view封裝成獨(dú)立的view挤牛。
如何驗(yàn)證?
可能會(huì)有同學(xué)認(rèn)為并沒有創(chuàng)建一個(gè)新的view种蘸,而是把當(dāng)前的view的狀態(tài)恢復(fù)到了初始狀態(tài)墓赴?這個(gè)疑問非常的合情合理竞膳,我們用一個(gè)例子來驗(yàn)證一下:
大家先看下邊的代碼:
struct Example3: View {
@State private var theId = 0
var body: some View {
VStack(spacing: 20) {
MyCircle()
.transition(AnyTransition.opacity.combined(with: .slide))
.id(theId)
.onDisappear {
print("消失了")
}
Text("id = \(theId) ")
Button("Increment Id") {
withAnimation(.easeIn(duration: 2.0)) {
self.theId += 1
}
}
}
}
struct MyCircle: View {
private let color: Color = [.red, .green, .blue, .purple, .orange, .pink, .yellow].randomElement()!
var body: some View {
return Circle()
.foregroundColor(color)
.frame(width: 180, height: 180)
}
}
}
運(yùn)行后,效果圖:
我們先假設(shè)诫硕,并不會(huì)創(chuàng)建新的view坦辟,而是每次都初始狀態(tài),我們自定義的MyCircle的初始狀態(tài)都會(huì)生成一個(gè)隨機(jī)的顏色章办,按照我們這個(gè)假設(shè)锉走,每次刷新,應(yīng)該只是不同顏色之間的變換纲菌,效果如下圖所示:
但實(shí)際上是這樣的效果:
之所以有這樣的效果,跟這行代碼有關(guān):
.transition(AnyTransition.opacity.combined(with: .slide))
如果不知道transition的同學(xué)疮绷,可以去看我的這篇文章https://zhuanlan.zhihu.com/p/146333076翰舌。
再看打印結(jié)果:
消失了
消失了
...
消失了
綜上所述,修改了標(biāo)識(shí)變量后冬骚,確實(shí)重新創(chuàng)建了一個(gè)新的view椅贱。
一個(gè)使用案例
從思想上來說,我們使用該技術(shù)的目的是重置自定義view的狀態(tài)只冻。在本小節(jié)中庇麦,我們演示另外一種用處:可以提升List View的性能。
在SwiftUI中喜德,每次刷新List View時(shí)山橄,系統(tǒng)都會(huì)計(jì)算刷新前和刷新后的變化,以便進(jìn)行一些類似于動(dòng)畫這樣的操作舍悯。但是航棱,當(dāng)List中的數(shù)據(jù)非常多的時(shí)候,系統(tǒng)計(jì)算這些變化就會(huì)非常耗時(shí)萌衬,我們先看下邊的代碼:
struct Example4: View {
@State private var array = (0..<500).map { _ in String.random() }
var body: some View {
VStack {
List(array, id: \.self) { item in
Text("\(item)")
}
Button("Shuffle") {
self.array.shuffle()
}
}
}
}
extension String {
static func random(length: Int = 20) -> String {
String((0..<length).map { _ in "ABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890".randomElement()! })
}
}
當(dāng)點(diǎn)擊了Shuffle按鈕后饮醇,需要等5秒左右的時(shí)間才能刷新,我們只需要簡單的修改代碼秕豫,給List綁定一個(gè)值就可以了朴艰,注意,這里的List相當(dāng)于一個(gè)獨(dú)立的view混移。
struct Example5: View {
@State private var theId = 0
@State private var array = (0..<500).map { _ in String.random() }
var body: some View {
VStack {
List(array, id: \.self) { item in
Text("\(item)")
}.id(theId)
Button("Shuffle") {
self.array.shuffle()
self.theId += 1
}
}
}
}
但在SwiftUI中祠墅,因?yàn)樾陆艘粋€(gè)view,因此List會(huì)滾動(dòng)到最上邊歌径,這又是一個(gè)新的問題饵隙。
總結(jié)
使用id()的一個(gè)核心思想是,當(dāng)我們修改了綁定值后沮脖,會(huì)立馬新建一個(gè)相同的view金矛,以至于表面看上去芯急,該view像回到了初始狀態(tài),在我們平時(shí)開發(fā)中驶俊,當(dāng)遇到此場(chǎng)景時(shí)娶耍,可以考慮使用該技術(shù)。
注:上邊的內(nèi)容參考了網(wǎng)站https://swiftui-lab.com/swiftui-id/饼酿,如有侵權(quán)榕酒,立即刪除。