SwiftUI之id(_)如何標(biāo)識(shí)View

本篇文章講解的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è)例子:

Kapture 2020-06-27 at 10.40.07.gif

這個(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)很多呢橡娄?類似下邊這種情況:

企業(yè)微信截圖_a5c93d73-b513-4677-be97-6bfe88d41049.png
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):

Kapture 2020-06-27 at 11.04.46.gif

點(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
            }
        }
    }
}
Kapture 2020-06-27 at 11.09.08.gif

通過上邊的實(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)行后,效果圖:

企業(yè)微信截圖_c1565004-be14-4457-ac97-adeb202d1b9b.png

我們先假設(shè)诫硕,并不會(huì)創(chuàng)建新的view坦辟,而是每次都初始狀態(tài),我們自定義的MyCircle的初始狀態(tài)都會(huì)生成一個(gè)隨機(jī)的顏色章办,按照我們這個(gè)假設(shè)锉走,每次刷新,應(yīng)該只是不同顏色之間的變換纲菌,效果如下圖所示:

Kapture 2020-06-27 at 11.21.40.gif

但實(shí)際上是這樣的效果:

Kapture 2020-06-27 at 11.23.38.gif

之所以有這樣的效果,跟這行代碼有關(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()! })
    }
}
Simulator Screen Shot - iPhone 11 Pro Max - 2020-06-27 at 11.33.00.png

當(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)榕酒,立即刪除。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末故俐,一起剝皮案震驚了整個(gè)濱河市想鹰,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌药版,老刑警劉巖辑舷,帶你破解...
    沈念sama閱讀 223,002評(píng)論 6 519
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異槽片,居然都是意外死亡何缓,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,357評(píng)論 3 400
  • 文/潘曉璐 我一進(jìn)店門还栓,熙熙樓的掌柜王于貴愁眉苦臉地迎上來碌廓,“玉大人,你說我怎么就攤上這事剩盒」绕牛” “怎么了?”我有些...
    開封第一講書人閱讀 169,787評(píng)論 0 365
  • 文/不壞的土叔 我叫張陵辽聊,是天一觀的道長波材。 經(jīng)常有香客問我,道長身隐,這世上最難降的妖魔是什么廷区? 我笑而不...
    開封第一講書人閱讀 60,237評(píng)論 1 300
  • 正文 為了忘掉前任,我火速辦了婚禮贾铝,結(jié)果婚禮上隙轻,老公的妹妹穿的比我還像新娘。我一直安慰自己垢揩,他們只是感情好玖绿,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,237評(píng)論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著叁巨,像睡著了一般斑匪。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上锋勺,一...
    開封第一講書人閱讀 52,821評(píng)論 1 314
  • 那天蚀瘸,我揣著相機(jī)與錄音狡蝶,去河邊找鬼。 笑死贮勃,一個(gè)胖子當(dāng)著我的面吹牛贪惹,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播寂嘉,決...
    沈念sama閱讀 41,236評(píng)論 3 424
  • 文/蒼蘭香墨 我猛地睜開眼奏瞬,長吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了泉孩?” 一聲冷哼從身側(cè)響起硼端,我...
    開封第一講書人閱讀 40,196評(píng)論 0 277
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎寓搬,沒想到半個(gè)月后珍昨,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,716評(píng)論 1 320
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡订咸,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,794評(píng)論 3 343
  • 正文 我和宋清朗相戀三年曼尊,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了酬诀。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片脏嚷。...
    茶點(diǎn)故事閱讀 40,928評(píng)論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖瞒御,靈堂內(nèi)的尸體忽然破棺而出父叙,到底是詐尸還是另有隱情,我是刑警寧澤肴裙,帶...
    沈念sama閱讀 36,583評(píng)論 5 351
  • 正文 年R本政府宣布趾唱,位于F島的核電站,受9級(jí)特大地震影響蜻懦,放射性物質(zhì)發(fā)生泄漏甜癞。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,264評(píng)論 3 336
  • 文/蒙蒙 一宛乃、第九天 我趴在偏房一處隱蔽的房頂上張望悠咱。 院中可真熱鬧,春花似錦征炼、人聲如沸析既。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,755評(píng)論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽眼坏。三九已至,卻和暖如春酸些,著一層夾襖步出監(jiān)牢的瞬間宰译,已是汗流浹背檐蚜。 一陣腳步聲響...
    開封第一講書人閱讀 33,869評(píng)論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留囤屹,地道東北人熬甚。 一個(gè)月前我還...
    沈念sama閱讀 49,378評(píng)論 3 379
  • 正文 我出身青樓,卻偏偏與公主長得像肋坚,于是被迫代替她去往敵國和親乡括。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,937評(píng)論 2 361